package com.example.springredisson;

import io.micrometer.core.instrument.util.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ParseException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Set;

/**
 * @author bsg
 * @date 2021/11/6
 * @description 自定义缓存 aop
 */
@Aspect
@Component
public class CacheLevelAspect {

    private static Logger logger = LoggerFactory.getLogger(CacheLevelAspect.class);


    private final SpelExpressionParser expressionParser = new SpelExpressionParser();

    /**
     * 配置织入点
     */
    @Pointcut("@annotation(com.example.springredisson.CacheLevel)")
    public void CachePointCut() {
    }

    @Autowired
    private CacheManager cacheManager;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 环绕
     *
     * @param jp
     * @param cacheLevel
     * @return
     * @throws Throwable
     */
    @Around("CachePointCut() && @annotation(cacheLevel)")
    public Object update(ProceedingJoinPoint jp, CacheLevel cacheLevel) throws Throwable {
        logger.info("缓存执行方法:{}", jp.getTarget());
        String value = cacheLevel.value();
        switch (cacheLevel.type()) {
            case DELETE:
                MethodSignature signature = (MethodSignature) jp.getSignature();
                String[] parameterNames = signature.getParameterNames();
                Method method = signature.getMethod();
                EvaluationContext context = new StandardEvaluationContext();
                Object[] args = jp.getArgs();
                //获取方法参数值
                for (int i = 0; i < args.length; i++) {
                    context.setVariable(parameterNames[i], args[i]);
                }
                String id = cacheLevel.id();
                // 执行原方法
                Object proceed1 = jp.proceed();
                Cache cache = cacheManager.getCache(value);
                // TODO 可使用消息队列清除其他服务器 缓存
                // id字段为空  则删除 value 下所有缓存
                if (StringUtils.isBlank(id)) {
                    // 删除 所有 以{cacheLevel.value()} 开头的所有 key
                    Set<String> keys = redisTemplate.keys(value + ":*");
                    redisTemplate.delete(keys);
                    cache.invalidate();
                    logger.info("所需删除的id值为空 删除全部缓存:{}", value + ":*");
                    return proceed1;
                }
                // 拿到id 具体值
                id=getValue(cacheLevel.id(), context, method);
                redisTemplate.delete(value + ":" + id);
                cache.evict(id);
                logger.info("删除缓存:{}", value + ":" + id);
                return proceed1;
            case SELECT:
                return selectCache(jp, cacheLevel);
            case INSERT:
                Object proceed = jp.proceed();
                return proceed;
            case UPDATE:
                Object proceed2 = jp.proceed();
                Cache cache1 = cacheManager.getCache(value);
                cache1.invalidate();
                return proceed2;
            default:
                return null;
        }
    }

    private String getValue(String spEl, EvaluationContext context, Method method) {
        String str = null;
        try {
            Expression expression = expressionParser.parseExpression(spEl);
            str = String.valueOf(expression.getValue(context));
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return str;
    }


    private Object selectCache(ProceedingJoinPoint jp, CacheLevel cacheLevel) throws Throwable {

        long startTime = System.currentTimeMillis();
        String id = cacheLevel.id();
        StringBuilder sbKey = new StringBuilder();
        // 获取请求参数
        Object[] args = jp.getArgs();
        // 获取参数键
        MethodSignature signature = (MethodSignature) jp.getSignature();
        String[] parameterNames = signature.getParameterNames();
        Method method = signature.getMethod();
        EvaluationContext context = new StandardEvaluationContext();
        //获取方法参数值
        for (int i = 0; i < args.length; i++) {
            context.setVariable(parameterNames[i], args[i]);
            sbKey.append(args[i]);
        }

        String key = sbKey.toString();;

        // id 有值 则  id 为缓存key
        if (StringUtils.isNotBlank(id)) {
            String value1 = getValue(cacheLevel.id(), context, method);
            key = value1;

        }
//        else if (cacheLevel.only() != null && cacheLevel.only().length > 0) {
//            for (String only : cacheLevel.only()) {
//                System.out.println(getValue(only, context, method));
//            }
//
//        }

        String value = cacheLevel.value();
        String s = value + ":" + key;
        Cache cache = cacheManager.getCache(value);
        // 获取本地缓存
        Cache.ValueWrapper valueWrapper = cache.get(key);
        if (valueWrapper != null) {
            Object o = valueWrapper.get();
            logger.info("获取本地缓存 key:{},value:{},执行时间:{}", s, o, System.currentTimeMillis() - startTime);
            return o;
        }
        // 远程缓存查找
        Object o = redisTemplate.opsForValue().get(s);
        if (o != null) {
            cache.put(key, o);
            logger.info("获取远端缓存 key:{},value:{},执行时间:{}", s, o, System.currentTimeMillis() - startTime);
            return o;
        }
        // 执行切面方法
        Object proceed = jp.proceed();
        logger.info("执行原方法 key:{},value:{},执行时间:{}", s, proceed, System.currentTimeMillis() - startTime);
        // 放远程缓存
        if (cacheLevel.time() < 0) {
            redisTemplate.opsForValue().set(s, proceed);
        } else {
            redisTemplate.opsForValue().set(s, proceed, cacheLevel.time(), cacheLevel.timeUnit());
        }
        // 放入本地缓存
        cache.put(key, proceed);
        return proceed;
    }

}
